<?php
  /* standards
   methods/functions use _ instead of - or camel case or one word i.e.
   walk_recursive
   not walkRecursive or walkrecursive etc.
   
    
  */
  function fn(){
   /*
     if only argument (str)arg used as body.
     if two arguments (str)args first is arglist second taken as body
   */
    $args = func_get_args();
    if(count($args) == 1){
      $arglist = '';
      $body = array_shift($args);
    }
    else{
      $arglist = array_shift($args);
      $body = array_shift($args);
    }
    return create_function($arglist,$body);
  }


  function fnx($body){
    /*
      (str)$body is body of method
    */
    return fn('$x',$body);
  }

  function sfnx($body){
    /*
      (str)$body with a single return value
    */
    return fnx('return '.$body.';');
  }

  function lambda($body){
    /* 
      alias of sfnx
    */
    return sfnx($body);
  }

  function ifnx($body){
    /*
     (str)$body of if clause returns bool
    */
    return fnx('return ('.$body.') ? true:false;');
  }
  
  class ArrayPlus{
    var $data = Array();
    
    function __construct($array=false){
      if(is_array($array)){
        $this->data = $array;
      }
      return $this;
    }
   
    function __get($data){
      //does the function exists?
      if(method_exists('ArrayPlus',$data)){
        return $this->$data();
      }
      else{
	echo 'Method not found';
        return $this;
      }
    }

    function all($body){
      #returns true if all elements meet the evaluation of $body. 
      $all = $this->count;
      return xH($this)->if_only($body)->count == $all ? true:false;      
    }

    function any($body){
      #returns true if ANY elements meet the conditional in $body
      return xH($this)->if_only($body)->count > 0 ? true:false;      
    }
   
    function arsort($sort_flags=SORT_REGULAR){
      #sorts elements in reverse order and maintains index association.
      arsort($this->data,$sort_flags);
      return $this;
    }
 
    function asort($sort_flags=SORT_REGULAR){
      #sorts elements and maintains index association.
      asort($this->data,$sort_flags);
      return $this;
    }

    function car(){
      #removes and returns the first element.
      return $this->shift();
    }
    
    function cdr(){
      #returns array of everything beside first element.
      $this->shift();
      return xH($this->out());
    }

    function count($total=false){
      #returns int indicating how many elements there are.
      return count($this->data);
    }

    function current(){
      #returns mixed, what ever current element is.
      return current($this->data);
    }

    function data(){
      #returns an array of all elements.
      return $this->data;
    }

    function diff($array){
      #only elements not existing in self or the $array remain.
      $this->data = array_diff($this->data,$this->get_data($array));
      return $this;
    }

    function expand($body,$stop=20){
      #Expands a set based on its members and the body passed in. 
      #Think of it as the opposite of reduce. 
      #Body expects the prior element to be $x and the current to be $y.

      $fn = fn('$x,$y',"return $body;");
      $x = $this->first(); 
      $z = false;
      while($this->count <= $stop){
        $y = $this->next();
        $z = $fn($x,$y);
        $this->push($z);
        $x = $y;
      }
      return $this;
    }

    function end(){
      #move cursor to end of array
      end($this->data);
      return $this;
    }

    function fill($start_key,$num,$val){
      #overwrites data with new array from start_key to start_key+num with val.
      $this->data = array_fill($start_key,$num,$val);
      return $this;
    }

    function flatten(){
      #attempt to flatten array recursively
      $newdata = Array();

      foreach($this->data() as $node){
        $node = $this->get_data($node);
        if(is_array($node)){
          $newdata = array_merge($newdata,$node);
        }
        else{
          $newdata[] = $node;
        }
      }
      $this->data = $newdata;

      return $this;
    }

    function filter($funcname){
      #removes/keeps elements based on evaluation in $funcname.
      $this->data = array_filter($this->data,$funcname);
      return $this;
    }
    
    function filterf($body){
      #removes/keeps elements based on evaluation of $body against each element $x.
      return $this->filter(ifnx($body));
    }

    
    function flip(){
      #exchanges values for keys
      $this->data = array_flip($this->data);
      return $this;    
    }

    function for_each($body,$output=false){
      #applies $body to each element $x.
      if($output){
        return $this->map(fnx('global '.$output.'; '.$body.' return $x;'));
      }
      else{
        return $this->mapf($body);
      }
    }

    function first(){
      #resets the array cursor and returns first element
      return $this->reset()->current();
    }

    function get($key,$defalt=false){
      #returns elements at $key, if not found returns value of $default.
      if(isset($this->data[$key]))
        return $this->data[$key];
      else
        return $default;
    }

    function get_data($object){
      #returns an array if passed an array, if passed an ArrayPlus object it returns the $data from it.

      if(is_object($object) && get_class($object) == 'ArrayPlus'){
        return $object->out();
      }
      else{
        return $object;
      }
    }

    function if_in($needle,$funcname){
     #if $needle is an element in array then call $funcname
 
     if($this->in($needle)){
        call_user_func($funcname);
      }
      return $this;
    }
    
    function if_in_map($needle,$funcname){
      #if $needle is an element in array then map $funcname to elements.

      if($this->in($needle)){
        $this->map($funcname);
      }
      return $this;
    }

    function if_only($body){
      #remove any element $x which does not meet the condition in $body.
      return $this->filterf($body);
    }

    function in(){
      #returns true if $value is found in $_data and false otherwise. 
      return (array_intersect(func_get_args(),$this->data)) ? true:false;
    }


    function implode($by=','){
      #returns string of elements seperated by $by
      return implode($by,$this->data);
    }

    function implode_wrap($start,$end,$by=','){
      #returns string of elements each surrounded by $start and $end 
      #and seperated with $by
      return implode($by,$this->for_each('$x = "'.$start.'{$x}'.$end.'"')->out);
    }

    function is_empty(){
      #returns bool if set is empty
      return empty($this->data);
    }

    function intersect($array){
      $this->data = array_intersect($this->data,$this->get_data($array));
      return $this;
    }

    function intersect_key($array){
      $this->data = array_intersect_key($this->data,$this->get_data($array));
      return $this;
    }
    
    function join($other,$func){
      //replace with array_intersect_ukey;
      $fn = function_exists($func) ? $func:fn('$x,$y','return '.$func.';');
      $this->data = array_intersect_ukey($this->data, $other, $fn);  
      return $this;
    }

    function keys(){
      return array_keys($this->data);
    }

    function key_in($needle){
      #returns true if key is in the $data
      return in_array($needle,$this->keys());
    }

    function map($funcname){
      #Expects $function to be the name of the function to map against the array. 

      $this->data = array_map($funcname,$this->data);
      return $this;
    }

    function mapf($body){
      #Expects $body to be the body of a function which takes a parameter $x 
      #which is mapped to the array.

      return $this->map(sfnx($body));
    }

    function out(){
      #returns the array stored in $data
      return $this->data();
    }

    function pad($pad_size,$pad_value=0){
      /* pad value defaults to 0 */
      $this->data = array_pad($this->data,$pad_size,$pad_value);
      return $this;
    }

    function printr(){
      #returns string which contains string representation of array.
      return print_r($this->data,1);
    }

    function range($low,$high=0,$step=1){
      if($low > 0 && $high == 0){
        $high = $low-1;
        $low = 0;
        $step = 1;
      }
      else{
        //$low--;
        //$high--;
      }
      
      $this->data = range($low,$high,$step);
      return $this;
    }

    function reset(){
      #resets the cursor of the array to the begining
      reset($this->data);
      return $this;
    }

    function reverse(){
      #reverses the elements in the array
      $this->data = array_reverse($this->data);
      return $this;        
    }

    function rsort(){
      rsort($this->data);
      return $this;
    }

    function reduce($body,$initial=1){
      #returns reduced data based on $body which evaluates vars $x and $y
      return array_reduce($this->data,fn('$x,$y','return '.$body.';'),$initial);
    }

    function set($key,$value){
      #set $key to $value
      $this->data[$key] = $value;
      return $this;
    }

    function sort($function=false,$sort_flags=SORT_REGULAR){
      #if no function is passed calls normal sort,
      #if function name is passed it calls that
      #if it is just a string it assumes this is
      #the body of an anonymous function
     
      if(function_exists($function)){
        usort($this->data,$function);          
      }
      else if($function){
        usort($this->data,fn('$x,$y','return '.$function.';'));
      }
      else{
        sort($this->data,$sort_flags);  
      }
      
      return $this;
    }

    function sum($start=0){
      #return sum of all elements.. call reduce?
      return $this->reduce('bcadd($x,$y)',$start);
    }

    function slice($offset,$length=false){
      $this->data = ($length) ? array_slice($this->data,$offset,$length) :
                                array_slice($this->data,$offset);
      return $this;
    }


    function next(){
      return next($this->data);
    }
    
    function prev(){
      prev($this->data());
      return $this;
    }

    function prior(){
      return $this->prev()->this->current();
    }
    
        
    
        
    function last(){
      return $this->end()->current();
    }
    

        

    function push(){
      foreach(func_get_args() as $arg){
        array_push($this->data,$arg);
      }
      return $this;
    }
    



    
        
    function not_empty(){
      return ($this->is_empty()) ? false:true;
    }
    
    
        

    
    
        
    function search($needle,$strict=false){
      $key = array_search($needle,$this->data,$strict);
      return $key;
    }
    
        
    
    function walk($funcname){
      $this->data = array_walk($this->data,$funcname);
      return $this;
    }
    
    function walk_recursive($funcname){
      $this->data = array_walk_recursive($this->data,$funcname);
      return $this;
    }
    
    
         
    function ksort(){
      ksort($this->data);
      return $this;
    }
        
    
    function merge(){
        
    }
    
    function merge_recursive(){
        
    }
    
        
    function length(){
      return count($this->data);
    }
    
    function splice(){
        
    }
    
  
    
        
    
        
        
        
    function show($by=' '){
      /* non destructive */
      $temp = xH($this->data);
      return '['.$temp->map(sfnx('(is_array($x)) ? "(".implode(",",$x).")":$x'))->implode(','.$by.'').']';
    }
     
    function show_keys($by=', '){
      $set = Array();
      foreach($this->data as $key=>$val)
        $set[] = "$key:$val";
      return '['.implode($by,$set).']';
    }
 


    
    
        
        
        
    function unique(){
      $this->data = array_unique($this->data);
      return $this;
    }
    

    
    function max(){
      //return largest element in list
      return xH($this)->asort()->pop();      
    }
    
    function min(){
      //return smallest element in list
      return xH($this)->arsort()->pop();      
    }
    

    
    function mul(){
      return $this->reduce('$x = bcmul($x,$y)');
    }
    
    function shift(){
      return array_shift($this->data);    
    }
    
    function unshift($val){
      array_unshift($this->data,$val);
      return $this;
    }
    
    

    
    function pop($fn=false){
      return array_pop($this->data);    
    }
    
    function tap($fn=false){
      $node = $this->shift();
      if($fn){
        return call_user_func_array($fn,Array($node));
      }      
    }
        
        
    function trim(){
      return $this->map('trim');
    }
    
    
    

  }

  

  function xH(){
    /*
     if arg is array passed in as data
     if arg is Hash object data of arg is passed in to hash
     if more than one arg it pushes each onto Hash
    */
    $args = func_get_args();
    $array = false;
    if(count($args) == 1){
      if(is_array($args[0]))
        $array = $args[0];
      else if(is_object($args[0]) && get_class($args[0]) == 'ArrayPlus'){
        $array = $args[0]->out();
      }
      else{
        $array = false;
      }
    }
      
    $new = new ArrayPlus($array);
    
    if(!$array && count($args) > 0){
      foreach($args as $arg){
        $arg = is_string($arg) ? trim($arg) : $arg;
        $new->push($arg);
      }
    }
    return $new;
  }
  
  function xE($string,$splitby=','){
    return xH(explode($splitby,$string));
  }
  
  function xJ($string){
    return xH(json_decode($string));
  }
  
  function xR($start,$end=0,$step=1){
    /*
      if only one arg assumes start to be 0 and first arg to be end
      also step must not be greater than end! 
    */    
    $new = new ArrayPlus();
    $new->range($start,$end,$step);
    return $new;
  }
  
  function xL($string){
    //explode by letter
    $string .= '';
    $new = new ArrayPlus();
    for($i=0; $i<strlen($string); $i++)
      $new->push($string{$i});
      
    return $new;
  }
  
  function xI($string='',$splitby=false){
    $new = new ArrayPlus();
    $rows = explode("\n",$string);
    foreach($rows as $row){
      if($splitby){
        $new->push(explode($splitby,trim($row)));
      }
      else{
        $new->push($row);
      }
    }
    return $new;
  }

  function rmul($nums){
    if(get_class($object) == 'ArrayPlus')$nums = $nums->out();
    return xH($nums)->reduce('$x = bcmul($x,$y)');
  }
?>
